import curses
from collections import defaultdict
from random import choice, random


# 定义按键映射
ACTIONS = ['Up', 'Left', 'Down', 'Right', 'Restart', 'Exit']
LETTER_CODES = [ord(ch) for ch in 'WASDRQwasdrq']
ACTION_MAP = dict(zip(LETTER_CODES, ACTIONS * 2))


# 获取用户输入
def get_user_action(keyboard):
    char = '-1'

    while char not in ACTION_MAP:
        char = keyboard.getch()

    return ACTION_MAP[char]


# 转置矩阵
def transpose(grid):
    return [list(row) for row in zip(*grid)]


# 反转矩阵
def invert(grid):
    return [row[::-1] for row in grid]


# 定义游戏类
class Game(object):
    def __init__(self, rows=4, cols=4, win=2048, start_tiles=2, keep_playing=True):
        self.rows = rows
        self.cols = cols
        self.win = win
        self.keep_playing = keep_playing
        self._grid = [[]]
        self._start_tiles = start_tiles
        self._score = 0
        self._best_score = 0
        self._won_flag = False

        self.reset()

    # 随机填充一个瓦片
    def _add_random_tile(self, pr_2=0.9):
        tile = 2 if random() < pr_2 else 4
        (r, c) = choice([(r, c)
                         for r in range(self.rows)
                         for c in range(self.cols)
                         if self._grid[r][c] == 0])
        self._grid[r][c] = tile

    # 判断是否可移动
    def can_move(self, direction):
        # 判断能否向左移动
        def can_move_left(row):
            # 判断能否移动一步
            def can_move_one_step(i):
                if row[i] == 0 and row[i + 1] != 0:
                    return True
                if row[i] != 0 and row[i + 1] == row[i]:
                    return True
                return False

            return any(can_move_one_step(i) for i in range(len(row) - 1))

        # 将所有判断转化为判断能否向左移动
        check = {}
        check['Left'] = lambda grid: any(can_move_left(row) for row in grid)
        check['Right'] = lambda grid: check['Left'](invert(grid))
        check['Up'] = lambda grid: check['Left'](transpose(grid))
        check['Down'] = lambda grid: check['Right'](transpose(grid))

        if direction in check:
            return check[direction](self._grid)
        else:
            return False

    # 移动操作
    def move(self, direction):
        # 将一行向左移动
        def move_left(row):
            # 收紧一行
            def tighten(row):
                new_row = [i for i in row if i != 0]
                new_row += [0 for _ in range(len(row) - len(new_row))]
                return new_row

            # 合并一行瓦片
            def merge(row):
                pair = False
                new_row = []

                for i in range(len(row)):
                    if pair:
                        new_row.append(2 * row[i])
                        self._score += 2 * row[i]
                        pair = False
                    else:
                        if i + 1 < len(row) and row[i] == row[i + 1]:
                            pair = True
                            new_row.append(0)
                        else:
                            new_row.append(row[i])

                return new_row

            return tighten(merge(tighten(row)))

        # 将所有操作转化为向左移动的操作
        moves = {}
        moves['Left'] = lambda grid: [move_left(row) for row in grid]
        moves['Right'] = lambda grid: invert(moves['Left'](invert(grid)))
        moves['Up'] = lambda grid: transpose(moves['Left'](transpose(grid)))
        moves['Down'] = lambda grid: transpose(moves['Right'](transpose(grid)))

        if direction in moves:
            if self.can_move(direction):
                self._grid = moves[direction](self._grid)
                self._add_random_tile()
                return True
            else:
                return False

    # 判断游戏是否获胜
    def has_won(self):
        return any(any(i >= self.win for i in row) for row in self._grid)
        
    # 判断游戏是否结束
    def is_game_over(self):
        return not any(self.can_move(move) for move in ACTIONS)

    # 绘图
    def draw(self, screen):
        help_tips1 = "(W)Up (A)Left (S)Down (D)Right"
        tips_width = len(help_tips1)
        help_tips2 = "(R)Restart (Q)Exit".center(tips_width)
        game_over_tips = "GAME OVER!".center(tips_width)
        game_intro = "Join the numbers and get to the {self.win} tile!".format(
            self=self)
        score_tips = "SCORE: {}    BEST: {}".format(
            self._score, self._best_score)
        win_tips = "YOU WIN!".center(tips_width)
        won_tips = "YOU WON!".center(tips_width)

        # 向屏幕添加一段字符串
        def cast(string):
            screen.addstr(string + '\n')

        # 绘制水平分割线
        def draw_hor_sep():
            line = '+' + ('+------' * self.cols + '+')[1:]
            separator = defaultdict(lambda: line)

            if not hasattr(draw_hor_sep, "counter"):
                draw_hor_sep.counter = 0

            cast(separator[draw_hor_sep.counter])
            draw_hor_sep.counter += 1

        # 绘制行
        def draw_row(row):
            cast(''.join('|{: ^5} '.format(num)
                         if num > 0 else '|      ' for num in row) + '|')

        screen.clear()
        cast(game_intro)
        cast(score_tips)

        for row in self._grid:
            draw_hor_sep()
            draw_row(row)

        draw_hor_sep()

        if self.has_won():
            if self.keep_playing and self._won_flag:
                cast(won_tips)
            else:
                cast(win_tips)
                self._won_flag = True
        else:
            if self.is_game_over():
                cast(game_over_tips)
            else:
                cast(help_tips1)

        cast(help_tips2)

    # 重置游戏
    def reset(self, clear_best=False):
        if clear_best:
            self._best_score = 0
        elif self._score > self._best_score:
            self._best_score = self._score

        self.score = 0

        self._grid = [[0 for _ in range(self.cols)]
                      for _ in range(self.rows)]

        for _ in range(self._start_tiles):
            self._add_random_tile()


# 主函数
def main(stdscr):
    # 初始化状态
    def init():
        game.reset()
        return 'Game'

    # 结果状态
    def final(state):
        game.draw(stdscr)

        if game.keep_playing:
            return 'Game'

        action = get_user_action(stdscr)
        responses = defaultdict(lambda: state)
        responses['Restart'], responses['Exit'] = 'Init', 'Exit'
        return responses[action]

    # 游戏逻辑状态
    def game_logic():
        game.draw(stdscr)
        action = get_user_action(stdscr)

        if action == 'Restart':
            return 'Init'
        if action == 'Exit':
            return 'Exit'
        if game.move(action):
            if game.is_game_over():
                return 'GameOver'
            if game.has_won():
                return 'Win'
        return 'Game'

    # 定义状态，游戏在状态间转移
    state_actions = {
        'Init': init,
        'Win': lambda: final('Win'),
        'GameOver': lambda: final('GameOver'),
        'Game': game_logic
    }

    curses.use_default_colors()
    game = Game()
    state = 'Init'

    # 状态循环
    while state != 'Exit':
        state = state_actions[state]()


if __name__ == '__main__':
    curses.wrapper(main)
